#pragma once 

/* 实现用户管理子服务部分的封装 */

#include <brpc/server.h>
#include <butil/logging.h>
#include <odb/database.hxx>
#include <odb/mysql/database.hxx>
#include <brpc/channel.h>
#include <regex>    //使用正则表达式判断密码的合法性

#include "brpc.hpp"     //Rpc调用模块的封装
#include "etcd.hpp"     //服务注册模块封装
#include "logger.hpp"   //日志模块封装
#include "dms.hpp"      //验证码模块的封装
#include "data_es.hpp"  //ES数据管理客户端封装
#include "mysql_user.hpp"   //用户表的ODB映射操作
#include "data_redis.hpp"   //Redis数据管理客户端封装
#include "utils.hpp"    //工具类接口
#include "icsearch.hpp"     //ES接口的二次封装

#include "user.pb.h"    //protobuf框架代码
#include "base.pb.h"    //protobuf框架代码
#include "file.pb.h"    //protobuf框架代码
#include "user.hxx"     //ODB框架代码
#include "user-odb.hxx" //ODB框架代码


namespace ChatSystem
{

    // 1. 创建rpc服务子类继承pb(protobuf)中的UserService服务类，并实现内部的业务接口逻辑；
    class UserServiceImpl : public ChatSystem::UserService
    {
    public:
        UserServiceImpl(const std::shared_ptr<elasticlient::Client>& es_client,
            const std::shared_ptr<odb::core::database>& mysql_db,
            const std::shared_ptr<redis_t>& redis_client,
            const ServiceManager::ptr& channel_manager,
            const std::string& file_service_name,
            const DMSClient::ptr& dms_client)
            :_es_user(std::make_shared<ESUser>(es_client))
            ,_mysql_user(std::make_shared<UserTable>(mysql_db))
            ,_redis_session(std::make_shared<Session>(redis_client))
            ,_redis_status(std::make_shared<Status>(redis_client))
            ,_redis_codes(std::make_shared<Codes>(redis_client))
            ,_channel_manager(channel_manager)
            ,_file_service_name(file_service_name)
            ,_dms_client(dms_client)
        {
            //创建用户索引库
            _es_user->createIndex();
        }

        ~UserServiceImpl(){}

        //检查昵称的长度是否合法
        bool nickname_check(const std::string& nickname)
        {
            return nickname.size() > 2 && nickname.size() < 22 ;
        }
        //检查密码的合法性
        bool password_check(const std::string& password)
        {
            // 定义正则表达式模式---只包含字母、数字、特殊字符，限制长度6~15
            std::regex pattern("^[A-Za-z0-9!@#$%^&*(),.?\":{}|<>]{6,15}$");
    
            // 使用regex_match尝试匹配整个字符串
            return std::regex_match(password, pattern);
        }
        //检查手机号的格式是否正确
        bool phone_check(const std::string& phone)
        {
            // 定义正则表达式模式---手机号格式：必须以 1 开始，第二位 3~9 之间，后边 9 个数字字符
            std::regex pattern("^1[3-9]\\d{9}$");
    
            // 使用regex_match尝试匹配整个字符串
            return std::regex_match(phone, pattern);
        }

        //用户注册接口
        void UserRegister(google::protobuf::RpcController* controller,
            const ::ChatSystem::UserRegisterReq* request,
            ChatSystem::UserRegisterRsp* response,
            google::protobuf::Closure* done) override
        {
            LOG_DEBUG("收到用户注册请求！");
            //使用ClosureGurd类管理done对象，防止出现未调用Run方法；
            brpc::ClosureGuard rpc_gurd(done);
            //定义一个错误处理函数，当出错的时候被调用
            auto err_response = [this, response](const std::string& errmsg)
            {
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;
            };

            response->set_request_id(request->request_id());

            //1. 从请求中取出昵称和密码
            std::string nickname = request->nickname();
            std::string password = request->password();

            //2. 检查昵称的长度是否合法，长度限制在3~21个字符
            bool ret = nickname_check(nickname);
            if(ret == false)
            {
                LOG_ERROR("{} - 用户名长度不合法! ", request->request_id());
                return err_response("用户名长度不合法! ");
            }

            //3. 检查密码是否合法
            ret = password_check(password);
            if(ret == false)
            {
                LOG_ERROR("{} - 密码格式不合法！", request->request_id());
                return err_response("密码格式不合法！");
            }

            //TODO---这里就可以添加一个布隆过滤器
            //4. 根据昵称在数据库中进行判断是否昵称存在了
            auto user = _mysql_user->select_by_nickname(nickname);
            if(user)
            {
                //用户名已经存在了
                LOG_ERROR("{} - 用户名 {} 被占用！", request->request_id(), nickname);
                return err_response("用户名被占用！");
            }

            //5. 向数据库新增数据
            std::string uid = Util::uuid();
            user = std::make_shared<User>(uid, nickname, password);
            ret = _mysql_user->insert(user);
            if(ret == false)
            {
                LOG_ERROR("{} - MySQL数据库新增数据失败!", request->request_id());
                return err_response("MySQL数据库新增数据失败!");
            }

            //6. 向ES服务器中新增用户信息
            ret = _es_user->appendData(uid, nickname);
            if(ret == false)
            {
                LOG_ERROR("{} - ES搜索引擎新增数据失败!", request->request_id());
                return err_response("ES搜索引擎新增数据失败!");
            }
            //7.组织响应，进行成功与否的响应即可
            response->set_success(true);
        }

        //用户登录的接口
        void UserLogin(google::protobuf::RpcController* controller,
        const ::ChatSystem::UserLoginReq* request,
        ChatSystem::UserLoginRsp* response,
        google::protobuf::Closure* done) override
        {
            LOG_DEBUG("收到用户登录请求！");
            //使用ClosureGurd类管理done对象，防止出现未调用Run方法；
            brpc::ClosureGuard rpc_gurd(done);
            //定义一个错误处理函数，当出错的时候被调用
            auto err_response = [this, response](const std::string& errmsg)
            {
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;
            };

            response->set_request_id(request->request_id());

            //1. 从请求中取出昵称和密码
            std::string nickname = request->nickname();
            std::string password = request->password();

            //2. 通过昵称获取用户信息，进行密码是否一致的判断
            auto user = _mysql_user->select_by_nickname(nickname);
            // if(!user)
            // {
            //     //用户名不存在了
            //     LOG_ERROR("{} - 用户 {} 不存在, 请先注册用户!", request->request_id(), nickname);
            //     return err_response("用户名不存在!");
            // }
            // if(*user->password() != password)
            // {
            //     //密码不匹配
            //     LOG_ERROR("{} - 用户 {} 密码错误，请重新输入!", request->request_id(), nickname);
            //     return err_response("密码错误!");
            // }
            //大公司的报错都是用户名或者密码错误，并不会直接告诉你用户名错了还是密码错了，可能是为了安全吧
            if(!user || user->password() != password)
            {
                LOG_ERROR("{} - 用户名或密码错误： {} - {}!", request->request_id(), nickname, password);
                return err_response("用户名或密码错误!");
            }

            //3. 根据 redis 中的登录标记信息是否存在判断用户是否已经登录。
            bool ret = _redis_status->exists(user->user_id());
            if(ret == true)
            {
                LOG_ERROR("{} - 用户 {} 已在其他地方登录!", request->request_id(), nickname);
                return err_response("用户已在其他地方登录!");
            }

            //4. 构造会话 ID，生成会话键值对，向 redis 中添加会话信息以及登录标记信息
            std::string ssid = Util::uuid();    //构建一个会话ID
            _redis_session->append(ssid, user->user_id());
            //5. 组织响应，返回生成的会话 ID
            response->set_success(true);
            response->set_login_session_id(ssid);
        }

        //获取手机号验证码的接口
        void GetPhoneVerifyCode(google::protobuf::RpcController* controller,
        const ::ChatSystem::PhoneVerifyCodeReq* request,
        ChatSystem::PhoneVerifyCodeRsp* response,
        google::protobuf::Closure* done) override
        {
            LOG_DEBUG("收到获取手机号验证码请求！");
            //使用ClosureGurd类管理done对象，防止出现未调用Run方法；
            brpc::ClosureGuard rpc_gurd(done);
            //定义一个错误处理函数，当出错的时候被调用
            auto err_response = [this, response](const std::string& errmsg)
            {
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;
            };

            response->set_request_id(request->request_id());

            //1. 从请求中取出手机号码
            std::string phone = request->phone_number();

            //2. 验证手机号码格式是否正确（必须以 1 开始，第二位 3~9 之间，后边 9 个数字字符）
            bool ret = phone_check(phone);
            if(ret == false)
            {
                LOG_ERROR("{} - 手机号 {} 格式错误！", request->request_id(), phone);
                return err_response("手机号格式错误!");
            }

            //3. 生成 4 位随机验证码
            std::string code_id = Util::uuid();
            // std::string code = Util::vcode();
             std::string code = "1024";

            //TODO---验证码平台这块还没有实现，可以设置一个固定的值，不要获取验证码了
            // //4. 基于短信平台 SDK 发送验证码
            // ret = _dms_client->send(phone, code);
            // if(ret == false)
            // {
            //     LOG_ERROR("{} - 短信验证码发送失败 - {} !", request->request_id(), phone);
            //     return err_response("短信验证码发送失败！");
            // }
            //固定验证码为 1024

            //5. 构造验证码 ID，添加到 redis 验证码映射键值索引中
            ret = _redis_codes->append(code_id, code, std::chrono::seconds(300));
            if(ret == false)
            {
                LOG_ERROR("{} - 短信验证码添加redis管理失败 - {}-{} !", request->request_id(), code_id, code);
                return err_response("短信验证码添加redis管理失败!");
            }
            //6. 组织响应，返回生成的验证码 ID
            response->set_success(true);
            response->set_verify_code_id(code_id);
        }

        //手机号注册的接口
        void PhoneRegister(google::protobuf::RpcController* controller,
        const ::ChatSystem::PhoneRegisterReq* request,
        ChatSystem::PhoneRegisterRsp* response,
        google::protobuf::Closure* done) override
        {
            //使用ClosureGurd类管理done对象，防止出现未调用Run方法；
            brpc::ClosureGuard rpc_gurd(done);
            //定义一个错误处理函数，当出错的时候被调用
            auto err_response = [this, response](const std::string& errmsg)
            {
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;
            };

            response->set_request_id(request->request_id());

            //1. 从请求中取出手机号码和验证码 ID，以及验证码
            std::string phone = request->phone_number();
            std::string code_id = request->verify_code_id();
            std::string code = request->verify_code();

            //2. 检查注册手机号码是否合法
            bool ret = phone_check(phone);
            if(ret == false)
            {
                LOG_ERROR("{} - 手机号 {} 格式错误！", request->request_id(), phone);
                return err_response("手机号格式错误!");
            }

            //3. 从 redis 数据库中进行验证码 ID-验证码一致性匹配， 验证码验证通过后需要从redis中删除验证码信息
            auto vcode = _redis_codes->code(code_id);
            if(vcode != code)
            {
                LOG_ERROR("{} - 验证码错误 - reids: {}-{}, 用户填写的验证码：{}!", request->request_id(), 
                code_id, vcode.value(), code);
                return err_response("验证码错误!");
            }
            _redis_codes->remove(code_id);  //验证码验证通过后需要从redis中删除验证码信息
            //4. 通过数据库查询判断手机号是否已经注册过
            auto user = _mysql_user->select_by_phone(phone);
            if(user)
            {
                LOG_ERROR("{} - 该手机号已注册 - {}!", request->request_id(), phone);
                return err_response("该手机号已注册!");
            }
            
            //5. 向数据库新增用户信息
            std::string uid = Util::uuid();
            user = std::make_shared<User>(uid, phone);
            ret = _mysql_user->insert(user);
            if(ret == false)
            {
                LOG_ERROR("{} - MySQL数据库新增数据失败!", request->request_id());
                return err_response("MySQL数据库新增数据失败!");
            }

            //6. 向 ES 服务器中新增用户信息
            ret = _es_user->appendData(uid, uid, phone);
            if(ret == false)
            {
                LOG_ERROR("{} - ES搜索引擎新增数据失败!", request->request_id());
                return err_response("ES搜索引擎新增数据失败!");
            }

            //7. 组织响应，返回注册成功与否
            response->set_success(true);
        }

        //手机号登录的接口
        void PhoneLogin(google::protobuf::RpcController* controller,
        const ::ChatSystem::PhoneLoginReq* request,
        ChatSystem::PhoneLoginRsp* response,
        google::protobuf::Closure* done) override
        {
            LOG_DEBUG("收到手机号登录请求！");
            //使用ClosureGurd类管理done对象，防止出现未调用Run方法；
            brpc::ClosureGuard rpc_gurd(done);
            //定义一个错误处理函数，当出错的时候被调用
            auto err_response = [this, response](const std::string& errmsg)
            {
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;
            };

            response->set_request_id(request->request_id());

            //1. 从请求中取出手机号码和验证码 ID，以及验证码。
            std::string phone = request->phone_number();
            std::string code_id = request->verify_code_id();
            std::string code = request->verify_code();

            //2. 检查注册手机号码是否合法
            bool ret = phone_check(phone);
            if(ret == false)
            {
                LOG_ERROR("{} - 手机号 {} 格式错误！", request->request_id(), phone);
                return err_response("手机号格式错误!");
            }

            //3. 从 redis 数据库中进行验证码 ID-验证码一致性匹配，验证码验证通过后需要从redis中删除验证码信息
            auto vcode = _redis_codes->code(code_id);
            if(vcode != code)
            {
                LOG_ERROR("{} - 验证码错误 - reids: {}-{}, 用户填写的验证码：{}!", request->request_id(), 
                code_id, vcode.value(), code);
                return err_response("验证码错误!");
            }
            _redis_codes->remove(code_id);  //验证码验证通过后需要从redis中删除验证码信息

            //4. 根据手机号从数据数据进行用户信息查询，判断用用户是否存在
            auto user = _mysql_user->select_by_phone(phone);
            if(!user)
            {
                LOG_ERROR("{} - 该手机号未注册 - {}!", request->request_id(), phone);
                return err_response("该手机号未注册!");
            }

            //5. 根据 redis 中的登录标记信息是否存在判断用户是否已经登录。
            ret = _redis_status->exists(user->user_id());
            if(ret == true)
            {
                LOG_ERROR("{} - 手机号 {} 已在其他地方登录!", request->request_id(), phone);
                return err_response("手机号已在其他地方登录!");
            }

            //6. 构造会话 ID，生成会话键值对，向 redis 中添加会话信息以及登录标记信息
            std::string ssid = Util::uuid();    //构建一个会话ID
            _redis_session->append(ssid, user->user_id());

            //7. 组织响应，返回生成的会话 ID
            response->set_success(true);
            response->set_login_session_id(ssid);
        }

        //用户信息获取的接口---从这一步开始，就是用户登录之后会进行的操作，而登录状态的判断是由网关子服务完成的
        void GetUserInfo(google::protobuf::RpcController* controller,
        const ::ChatSystem::GetUserInfoReq* request,
        ChatSystem::GetUserInfoRsp* response,
        google::protobuf::Closure* done) override
        {
            LOG_DEBUG("收到用户信息获取请求！");
            //使用ClosureGurd类管理done对象，防止出现未调用Run方法；
            brpc::ClosureGuard rpc_gurd(done);
            //定义一个错误处理函数，当出错的时候被调用
            auto err_response = [this, response](const std::string& errmsg)
            {
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;
            };

            response->set_request_id(request->request_id());

            // 1. 从请求中取出用户 ID
            std::string uid = request->user_id();

            // 2. 通过用户 ID，从数据库中查询用户信息
            auto user = _mysql_user->select_by_user_id(uid);
            if(!user)
            {
                LOG_ERROR("{} - 未找到用户 {} 信息!", request->request_id(), uid);
                return err_response("未找到用户信息!");
            }

            // 3. 根据用户信息中的头像 ID，从文件服务器获取头像文件数据，组织完整用户信息
            //用户的头像不一定是存在的，如果用户没有上传头像就没有头像文件ID，所以需要判断用户头像ID是否存在
            UserInfo* user_info = response->mutable_user_info();
            user_info->set_user_id(uid);
            //下面的这些信息要存在才设置
            if(user->nickname().empty())
            {
                user_info->set_nickname(user->nickname());
            }
            if(user->description().empty())
            {
                user_info->set_description(user->description());
            }
            if(user->phone().empty())
            {
                user_info->set_phone(user->phone());
            }
            if(user->avatar_id().empty())
            {
                //如果头像文件ID存在，说明在文件存储子服务中有头像文件，我们就从文件存储子服务中获取头像文件数据
                //3.1 从信道管理对象中，获取到连接了文件存储子服务的channel
                auto channel = _channel_manager->choose(_file_service_name);
                if(!channel)
                {
                    LOG_ERROR("{} - 未找到文件管理子服务节点 - {}!", request->request_id(), _file_service_name);
                    return err_response("未找到文件管理子服务节点!");
                }
                //3.2 进行文件子服务Rpc请求，进行头像文件数据
                ChatSystem::FileService_Stub stub(channel.get());
                ChatSystem::GetSingleFileReq req;
                ChatSystem::GetSingleFileRsp rsp;
                req.set_request_id(request->request_id());
                req.set_file_id(user->avatar_id().c_str());

                brpc::Controller cntl;
                stub.GetSingleFile(&cntl, &req, &rsp, nullptr);
                if(cntl.Failed() || rsp.success() == false)
                {
                    LOG_ERROR("{} - 文件子服务 {} Rpc调用失败, {}!", request->request_id(), _file_service_name, cntl.ErrorText());
                    return err_response("文件子服务Rpc调用失败!");
                }
                //3.3 将头像文件数据放到响应的头像中
                user_info->set_avatar(rsp.file_data().file_content());
            }

            //4. 组织响应，返回用户信息设置头像
            response->set_success(true);
        }

        //获取多个用户信息的接口
        void GetMultiUserInfo(google::protobuf::RpcController* controller,
        const ::ChatSystem::GetMultiUserInfoReq* request,
        ChatSystem::GetMultiUserInfoRsp* response,
        google::protobuf::Closure* done) override
        {
            LOG_DEBUG("收到获取多个用户信息请求！");
            //使用ClosureGurd类管理done对象，防止出现未调用Run方法；
            brpc::ClosureGuard rpc_gurd(done);
            //定义一个错误处理函数，当出错的时候被调用
            auto err_response = [this, response](const std::string& errmsg)
            {
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;
            };

            response->set_request_id(request->request_id());

            //1. 从请求中取出用户 ID, 在多个用户信息获取的时候请求中的多个用户ID的列表
            std::vector<std::string> uid_lists;
            for(int i = 0; i < request->users_id_size(); ++i)
            {
                uid_lists.push_back(request->users_id(i));
            }
            //2. 通过用户 ID，从数据库中进行批量用户查询用户信息
            auto users = _mysql_user->select_multi_users(uid_lists);
            //有可能出现从MySQL数据库中取出的用户信息数量与请求中用户ID列表的数量不一致的问题
            if(users.size() != uid_lists.size())
            {
                LOG_ERROR("{} - 从MySQL数据库中查找的用户信息数量与请求中用户ID的数量不一致 {}-{}!", 
                    request->request_id(), 
                    users.size(), uid_lists.size());
                return err_response("从MySQL数据库中查找的用户信息数量与请求中用户ID的数量不一致!");
            }
           
            //3. 批量从文件管理子服务进行文件下载
            //3.1 从信道管理对象中，获取到连接了文件存储子服务的channel
            auto channel = _channel_manager->choose(_file_service_name);
            if(!channel)
            {
                LOG_ERROR("{} - 未找到文件管理子服务节点 - {}!", request->request_id(), _file_service_name);
                return err_response("未找到文件管理子服务节点!");
            }
            ChatSystem::FileService_Stub stub(channel.get());
            ChatSystem::GetMultiFileReq req;
            ChatSystem::GetMultiFileRsp rsp;
            req.set_request_id(request->request_id());
            for(auto& user : users)
            {
                req.add_file_id_list(user.avatar_id());      
            }
            brpc::Controller cntl;
            stub.GetMultiFile(&cntl, &req, &rsp, nullptr);
            if(cntl.Failed() || rsp.success() == false)
            {
                LOG_ERROR("{} - 文件子服务 {} Rpc调用失败, {}!", request->request_id(), _file_service_name, cntl.ErrorText());
                return err_response("文件子服务Rpc调用失败!");
            }
            
            //4. 组织响应，返回用户信息
            auto response_users_map = response->mutable_users_info();   //要组织的响应
            auto file_rsp_map = rsp.mutable_file_data();       //文件子服务的响应
            for(auto& user : users)
            {
                UserInfo user_info;
                user_info.set_user_id(user.user_id());
                user_info.set_nickname(user.nickname());
                user_info.set_description(user.description());
                user_info.set_phone(user.phone());
                user_info.set_avatar((*file_rsp_map)[user.avatar_id()].file_content());
                (*response_users_map)[user_info.user_id()] = user_info;
            }
            response->set_success(true);
        }

        //修改用户头像的接口
        void SetUserAvatar(google::protobuf::RpcController* controller,
        const ::ChatSystem::SetUserAvatarReq* request,
        ChatSystem::SetUserAvatarRsp* response,
        google::protobuf::Closure* done) override
        {
            LOG_DEBUG("收到修改用户头像请求！");
            //使用ClosureGurd类管理done对象，防止出现未调用Run方法；
            brpc::ClosureGuard rpc_gurd(done);
            //定义一个错误处理函数，当出错的时候被调用
            auto err_response = [this, response](const std::string& errmsg)
            {
                response->set_success(false);
                response->set_errmsg(errmsg);
            };

            response->set_request_id(request->request_id());

            //1. 从请求中取出用户 ID
            std::string uid = request->user_id();
            //2. 从数据库通过用户 ID 进行用户信息查询，判断用户是否存在
            auto user = _mysql_user->select_by_user_id(uid);
            if(!user)
            {
                LOG_ERROR("{} - 未找到用户 {} 信息!", request->request_id(), uid);
                return err_response("未找到用户信息!");
            }

            //3. 上传头像文件到文件子服务
            //3.1 从信道管理对象中，获取到连接了文件存储子服务的channel
            auto channel = _channel_manager->choose(_file_service_name);
            if(!channel)
            {
                LOG_ERROR("{} - 未找到文件管理子服务节点 - {}!", request->request_id(), _file_service_name);
                return err_response("未找到文件管理子服务节点!");
            }
            ChatSystem::FileService_Stub stub(channel.get());
            ChatSystem::PutSingleFileReq req;
            req.set_request_id(request->request_id());
            req.mutable_file_data()->set_file_name("");
            req.mutable_file_data()->set_file_size(request->avatar().size());
            req.mutable_file_data()->set_file_content(request->avatar());

            brpc::Controller cntl;
            ChatSystem::PutSingleFileRsp rsp;
            stub.PutSingleFile(&cntl, &req, &rsp, nullptr);
            if(cntl.Failed() || rsp.success() == false)
            {
                LOG_ERROR("{} - 文件子服务 {} Rpc调用失败, {}!", request->request_id(), _file_service_name, cntl.ErrorText());
                return err_response("文件子服务Rpc调用失败!");
            }
            std::string avatar_id = rsp.file_info().file_id();
            //4. 将返回的头像文件 ID 更新到数据库中
            user->avatar_id(avatar_id);
            auto ret = _mysql_user->update(user);
            if(ret == false)
            {
                LOG_ERROR("{} - 更新MySQL数据库用户头像ID失败 - {}!", request->request_id(), avatar_id);
                return err_response("更新MySQL数据库用户头像ID失败!");
            }
            //5. 更新 ES 服务器中用户信息
            //注意使用odb::nullable管理的string类型一定要判断是否为空，否则会出现对象为null的时候被管理的string未初始化的情况，使得字符串非常的长而出错；
            ret = _es_user->appendData(uid, 
                user->nickname(), 
                user->phone(), 
                user->description(), 
                avatar_id);
            if(ret == false)
            {
                LOG_ERROR("{} - 更新ES搜索引擎用户头像ID失败 - {}!", request->request_id(), avatar_id);
                return err_response("更新ES搜索引擎用户头像ID失败!");
            }
            //6. 组织响应，返回更新成功与否
            response->set_success(true);
        }

        //修改用户昵称的接口
        void SetUserNickname(google::protobuf::RpcController* controller,
        const ::ChatSystem::SetUserNicknameReq* request,
        ChatSystem::SetUserNicknameRsp* response,
        google::protobuf::Closure* done) override
        {
            LOG_DEBUG("收到修改用户昵称请求！");
            //使用ClosureGurd类管理done对象，防止出现未调用Run方法；
            brpc::ClosureGuard rpc_gurd(done);
            //定义一个错误处理函数，当出错的时候被调用
            auto err_response = [this, response](const std::string& errmsg)
            {
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;
            };

            response->set_request_id(request->request_id());
            
            //1. 从请求中取出用户 ID 与新的昵称
            std::string uid = request->user_id();
            std::string nickname = request->nickname();

            //2. 判断昵称格式是否正确
            bool ret = nickname_check(nickname);
            if(ret == false)
            {
                LOG_ERROR("{} - 用户名长度不合法! ", request->request_id());
                return err_response("用户名长度不合法! ");
            }

            //3. 从数据库通过用户 ID 进行用户信息查询，判断用户是否存在
            auto user = _mysql_user->select_by_user_id(uid);
            if(!user)
            {
                LOG_ERROR("{} - 未找到用户 {} 信息!", request->request_id(), uid);
                return err_response("未找到用户信息!");
            }

            //4. 将新的昵称更新到数据库中
            user->nickname(nickname);
            ret = _mysql_user->update(user);
            if(ret == false)
            {
                LOG_ERROR("{} - 更新MySQL数据库用户昵称失败 - {}!", request->request_id(), nickname);
                return err_response("更新MySQL数据库用户昵称失败!");
            }
            //5. 更新 ES 服务器中用户信息
            ret = _es_user->appendData(uid, 
                user->nickname(), 
                user->phone(), 
                user->description(), 
                user->avatar_id());
            if(ret == false)
            {
                LOG_ERROR("{} - 更新ES搜索引擎用户昵称失败 - {}!", request->request_id(), nickname);
                return err_response("更新ES搜索引擎用户昵称失败!");
            }
            //6. 组织响应，返回更新成功与否
            response->set_success(true);
        }

        //修改用户签名的接口
        void SetUserDescription(google::protobuf::RpcController* controller,
        const ::ChatSystem::SetUserDescriptionReq* request,
        ChatSystem::SetUserDescriptionRsp* response,
        google::protobuf::Closure* done) override
        {
            LOG_DEBUG("收到修改用户签名请求！");
            //使用ClosureGurd类管理done对象，防止出现未调用Run方法；
            brpc::ClosureGuard rpc_gurd(done);
            //定义一个错误处理函数，当出错的时候被调用
            auto err_response = [this, response](const std::string& errmsg)
            {
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;
            };

            response->set_request_id(request->request_id());

            //1. 从请求中取出用户 ID 与新的签名
            std::string uid = request->user_id();
            std::string description = request->description();

            //2. 从数据库通过用户 ID 进行用户信息查询，判断用户是否存在
            auto user = _mysql_user->select_by_user_id(uid);
            if(!user)
            {
                LOG_ERROR("{} - 未找到用户 {} 信息!", request->request_id(), uid);
                return err_response("未找到用户信息!");
            }

            //3. 将新的签名更新到数据库中
            user->description(description);
            auto ret = _mysql_user->update(user);
            if(ret == false)
            {
                LOG_ERROR("{} - 更新MySQL数据库用户签名失败 - {}!", request->request_id(), description);
                return err_response("更新MySQL数据库用户签名失败!");
            }
            //4. 更新 ES 服务器中用户信息
            ret = _es_user->appendData(uid, 
                user->nickname(), 
                user->phone(), 
                user->description(), 
                user->avatar_id());
            if(ret == false)
            {
                LOG_ERROR("{} - 更新ES搜索引擎用户签名失败 - {}!", request->request_id(), description);
                return err_response("更新ES搜索引擎用户签名失败!");
            }
            //5. 组织响应，返回更新成功与否
            response->set_success(true);
            
        }

        //修改用户手机号的接口
        void SetUserPhoneNumber(google::protobuf::RpcController* controller,
        const ::ChatSystem::SetUserPhoneNumberReq* request,
        ChatSystem::SetUserPhoneNumberRsp* response,
        google::protobuf::Closure* done) override
        {
            LOG_DEBUG("收到修改用户手机号请求！");
            //使用ClosureGurd类管理done对象，防止出现未调用Run方法；
            brpc::ClosureGuard rpc_gurd(done);
            //定义一个错误处理函数，当出错的时候被调用
            auto err_response = [this, response](const std::string& errmsg)
            {
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;
            };

            response->set_request_id(request->request_id());

            //1. 从请求中取出手机号码和验证码 ID，以及验证码。
            std::string uid = request->user_id();
            std::string phone = request->phone_number();
            std::string code_id = request->verify_code_id();
            std::string code = request->verify_code();

            //2. 检查修改的手机号码是否合法
            bool ret = phone_check(phone);
            if(ret == false)
            {
                LOG_ERROR("{} - 手机号 {} 格式错误！", request->request_id(), phone);
                return err_response("手机号格式错误!");
            }

            //3. 从 redis 数据库中进行验证码 ID-验证码一致性匹配，验证码验证通过后，要从Redis数据库中删除验证码信息
            auto vcode = _redis_codes->code(code_id);
            if(vcode != code)
            {
                LOG_ERROR("{} - 验证码错误 - reids: {}-{}, 用户填写的验证码：{}!", request->request_id(), 
                code_id, vcode.value(), code);
                return err_response("验证码错误!");
            }
            _redis_codes->remove(code_id);  //验证码验证通过后需要从redis中删除验证码信息

            //4. 从数据库通过用户 ID 进行用户信息查询，判断用户是否存在
            auto user = _mysql_user->select_by_user_id(uid);
            if(!user)
            {
                LOG_ERROR("{} - 未找到用户 {} 信息!", request->request_id(), uid);
                return err_response("未找到用户信息!!");
            }

            //5. 将新的手机号更新到数据库中
            user->phone(phone);
            ret = _mysql_user->update(user);
            if(ret == false)
            {
                LOG_ERROR("{} - 更新MySQL数据库用户手机号失败 - {}!", request->request_id(), phone);
                return err_response("更新MySQL数据库用户手机号失败!");
            }

            //6. 更新 ES 服务器中用户信息
            ret = _es_user->appendData(uid, 
                user->nickname(), 
                user->phone(), 
                user->description(), 
                user->avatar_id());
            if(ret == false)
            {
                LOG_ERROR("{} - 更新ES搜索引擎用户手机号失败 - {}!", request->request_id(), phone);
                return err_response("更新ES搜索引擎用户手机号失败!");
            }

            //7. 组织响应，返回更新成功与否
            response->set_success(true);
            
        }
    private:
        //这些是由内部构造的
        ChatSystem::UserTable::ptr _mysql_user;     //MySQL的user表， 一定是先实例化odb操作句柄，在实例化这个，注意这个顺序；
        std::shared_ptr<ESUser> _es_user;     //ES的用户索引
        ChatSystem::Session::ptr _redis_session;    //会话管理
        ChatSystem::Status::ptr _redis_status;  //状态管理
        ChatSystem::Codes::ptr _redis_codes;  //验证码管理

        //这些是Rpc调用客户端相关对象
        std::string _file_service_name; //文件子服务名称
        ServiceManager::ptr _channel_manager;    //Rpc调用中信道管理对象

        DMSClient::ptr _dms_client;     //验证码客户端
    };



    //用户管理子服务器搭建的流程类
    class UserServer
    {
    public:
        using ptr = std::shared_ptr<UserServer>;

        UserServer(const Discovery::ptr& discovery_client, 
            const Registry::ptr& registry_client, 
            const std::shared_ptr<elasticlient::Client>& es_client,
            const std::shared_ptr<odb::core::database>& mysql_client,
            const std::shared_ptr<redis_t>& redis_client,
            const std::shared_ptr<brpc::Server>& rpc_server)
        :_discovery_client(discovery_client)
        , _registry_client(registry_client)
        ,_es_client(es_client)
        ,_mysql_client(mysql_client)
        ,_redis_client(redis_client)
        , _rpc_server(rpc_server)
        {}
        ~UserServer(){}

        //搭建RPC服务器，并启动服务器
        /* make相关接口的调用必须在start接口调用之前，
        因为start涉及到了启动服务器，而启动服务器的前提是客户端对象和服务注册客户端对象已经实例化好了; 
        为了防止使用者在start接口调用之前未调用 make相关接口而导致
        服务器无法启动的问题，我们可以用建造者模式，将构造过程进行控制，在代码中规定调用顺序，去倒逼使用者;
        使用建造者模式的原因还是由于构造userServer对象时参数太多，为了简化userServer对象的构造 */
        void start()
        {
            // 阻塞等待服务端运行结束
            _rpc_server->RunUntilAskedToQuit();
        }
    private:
        DMSClient::ptr _dms_client;     //验证码客户端
        Registry::ptr _registry_client;  //注册中心客户端
        Discovery::ptr _discovery_client;    //服务发现客户端
        std::shared_ptr<elasticlient::Client> _es_client;    //ES客户端
        std::shared_ptr<odb::core::database> _mysql_client;    //MySQL的ODB操作句柄
        std::shared_ptr<redis_t> _redis_client;     //Redis客户端
        std::shared_ptr<brpc::Server> _rpc_server;   //brpc服务器
    };

    //建造者模式---用来构造UserServer对象，将UserServer对象的真正构造过程给封装起来，通过封装的类去构造 UserServer对象
    class UserServerBuilder
    {
    public:
        //构造es客户端对象
        void make_es_object(const std::vector<std::string>& host_list)
        {
            _es_client = EsClientFactory::create(host_list);
        }
        //构造mysql客户端对象
        void make_mysql_object(const std::string& user,
            const std::string& password,
            const std::string& host,
            const std::string& db,
            const std::string& cset,
            int port,
            int connection_pool_count)
        {
           _mysql_client = ODBFactory::create(user, password, host, db, cset, port, connection_pool_count);
        }
        //构造redis客户端对象
        void make_redis_object(const std::string& host,
            int port,
            int db,
            bool keep_alive)
        {
            _redis_client = RedisClientFactory::creat(host, port, db, keep_alive);
        }
        //构造服务发现客户端对象和信道管理对象
        void make_discovery_object(const std::string& reg_host,
            const std::string& base_service_name,
            const std::string& file_service_name)
        {
            _file_service_name = file_service_name;
            _channel_manager = std::make_shared<ServiceManager>();
            _channel_manager->declared(_file_service_name);
            LOG_DEBUG("设置文件子服务为需要添加管理的子服务：{}", _file_service_name);
            auto put_cb = std::bind(&ChatSystem::ServiceManager::onServiceOnline, 
                _channel_manager.get(), 
                std::placeholders::_1, 
                std::placeholders::_2);
            auto del_cb = std::bind(&ChatSystem::ServiceManager::onServiceOffline, 
                _channel_manager.get(), 
                std::placeholders::_1,
                std::placeholders::_2);
            //2. 构造服务发现对象 
            _discovery_client = std::make_shared<ChatSystem::Discovery>(reg_host, base_service_name, put_cb, del_cb);
        }
        //构造验证码客户端对象
        void make_dms_object(const std::string& access_key_id,
            const std::string& access_key_secret)
        {
            _dms_client = std::make_shared<DMSClient>(access_key_id, access_key_secret);
        }


        //用于构建RPC服务器
        void make_rpc_server(uint16_t port, 
            uint32_t timeout,      //连接空闲超时时间---超时后连接关闭
            uint8_t num_threads)    //rpc服务器的线程数量---IO线程数量；
        {
            _rpc_server = std::make_shared<brpc::Server>();
            // _es_client 的实例化必须在 user_service之前进行
            if(!_es_client)
            {
                LOG_ERROR("还未初始化ES搜索模块！");
                abort();
            }
            if(!_mysql_client)
            {
                LOG_ERROR("还未初始化MySQL数据库模块！");
                abort();
            }
            if(!_redis_client)
            {
                LOG_ERROR("还未初始化Redis数据库模块！");
                abort();
            }
             if(!_dms_client)
            {
                LOG_ERROR("还未初始化短信验证平台！");
                abort();
            }
            if(!_channel_manager)
            {
                LOG_ERROR("还未初始化信道管理模块！");
                abort();
            }
            if(!_discovery_client)
            {
                LOG_ERROR("还未初始化服务发现模块！");
                abort();
            }
            // 向rpc服务器类中添加rpc子服务对象---告诉服务器收到什么请求用哪个接口处理；
            /*注意：如果在user_service对象是栈上创建的，AddService的第一个参数就是user_service对象的地址，当
            make_rpc_server函数调用结束后user_service这个对象是被销毁了的，而在 _rpc_server中会使用到 user_service对象
            此时就会出现段错误，所以我们这里需要在堆上创建对象，这样make_rpc_server函数调用结束后再使用user_service对象也不会错。*/
            UserServiceImpl* user_service = new UserServiceImpl(_es_client, 
                _mysql_client, 
                _redis_client, 
                _channel_manager, 
                _file_service_name,
                _dms_client);
            /* SERVER_DOESNT_OWN_SERVICE 表示添加服务失败时，服务器也不会删除服务器对象； SERVER_OWNS_SERVICE 表示添加服务失败时，
            服务器将负责删除服务对象，当我们在堆上创建user_service对象时需要使用SERVER_OWNS_SERVICE，就不会造成内存泄漏。 */
            int ret = _rpc_server->AddService(user_service, brpc::ServiceOwnership::SERVER_OWNS_SERVICE); 
            if(ret == -1)
            {
                LOG_ERROR("添加Rpc服务失败！ ");
                abort();
            }
            brpc::ServerOptions options;
            options.idle_timeout_sec - timeout;
            options.num_threads = num_threads;
            ret = _rpc_server->Start(port, &options);
            if(ret == -1)
            {
                LOG_ERROR("服务器启动失败！");
                abort();
            }
        }

         //用于构造服务注册客户端对象
        void make_registry_object(const std::string& reg_host,  //注册中心的地址
            const std::string& service_name,    //服务名称
            const std::string& access_host)     //提供服务器的主机的地址信息
        {
            if(!_rpc_server)
            {
                LOG_ERROR("还未初始化RPC服务器！");
                abort();
            }
            _registry_client = std::make_shared<Registry>(reg_host);
            _registry_client->registry(service_name, access_host);
        }

        //构造一个 UserServer对象
        UserServer::ptr build()
        {
            if(!_es_client)
            {
                LOG_ERROR("还未初始化ES搜索模块！");
                abort();
            }
            if(!_mysql_client)
            {
                LOG_ERROR("还未初始化MySQL数据库模块！");
                abort();
            }
            if(!_redis_client)
            {
                LOG_ERROR("还未初始化Redis数据库模块！");
                abort();
            }
            if(!_channel_manager)
            {
                LOG_ERROR("还未初始化信道管理模块！");
                abort();
            }
            if(!_discovery_client)
            {
                LOG_ERROR("还未初始化服务发现模块！");
                abort();
            }
            if(!_registry_client)
            {
                LOG_ERROR("还未初始化服务注册模块");
                abort();
            }
            if(!_rpc_server)
            {
                LOG_ERROR("还未初始化RPC服务器模块");
                abort();
            }

            UserServer::ptr server = std::make_shared<UserServer>(_discovery_client, 
                _registry_client, 
                _es_client,
                _mysql_client,
                _redis_client,
                _rpc_server);
            return server;
        }
    private:
        std::string _file_service_name;  //文件管理子服务名称

        std::shared_ptr<elasticlient::Client> _es_client;   //ES客户端
        std::shared_ptr<odb::core::database> _mysql_client; //MySQL的ODB操作句柄
        std::shared_ptr<redis_t> _redis_client; //Redis客户端

        ServiceManager::ptr _channel_manager;   //信道管理对象
        Registry::ptr _registry_client;  //注册中心客户端
        Discovery::ptr _discovery_client;    //服务发现客户端对象

        DMSClient::ptr _dms_client; //验证码客户端
        std::shared_ptr<brpc::Server> _rpc_server;   //brpc服务器
    };

}